Completed
Push — master ( e88c19...17669b )
by Rain
03:00
created

ComposePopupView.initOnShow   F

Complexity

Conditions 36
Paths > 20000

Size

Total Lines 267

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 36
nc 2937600
nop 7
dl 0
loc 267
rs 2
c 1
b 1
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like ComposePopupView.initOnShow often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
2
import window from 'window';
3
import _ from '_';
4
import $ from '$';
5
import ko from 'ko';
6
import key from 'key';
7
import Jua from 'Jua';
8
9
import {
10
	Capa, Magics, KeyState, ComposeType, StorageResultType,
11
	EditorDefaultType, Notification, SetSystemFoldersNotification,
12
	UploadErrorCode
13
} from 'Common/Enums';
14
15
import {
16
	trim, isArray, isNormal, createCommand, delegateRun,
17
	isNonEmptyArray, clearBqSwitcher, replySubjectAdd,
18
	encodeHtml, noopFalse, inFocus, delegateRunOnDestroy,
19
	pInt, isUnd
20
} from 'Common/Utils';
21
22
import {UNUSED_OPTION_VALUE} from 'Common/Consts';
23
import {bXMLHttpRequestSupported, bMobileDevice} from 'Common/Globals';
24
import {upload} from 'Common/Links';
25
import {i18n, getNotification, getUploadErrorDescByCode} from 'Common/Translator';
26
import {format as momentorFormat} from 'Common/Momentor';
27
import {getMessageFlagsFromCache, setMessageFlagsToCache, setFolderHash} from 'Common/Cache';
28
29
import {HtmlEditor} from 'Common/HtmlEditor';
30
31
import AppStore from 'Stores/User/App';
32
import SettingsStore from 'Stores/User/Settings';
33
import IdentityStore from 'Stores/User/Identity';
34
import AccountStore from 'Stores/User/Account';
35
import FolderStore from 'Stores/User/Folder';
36
import PgpStore from 'Stores/User/Pgp';
37
import MessageStore from 'Stores/User/Message';
38
import SocialStore from 'Stores/Social';
39
40
import Remote from 'Remote/User/Ajax';
41
42
import * as Settings from 'Storage/Settings';
43
import * as Events from 'Common/Events';
44
45
import {ComposeAttachmentModel} from 'Model/ComposeAttachment';
46
47
import {getApp} from 'Helper/Apps/User';
48
49
import {view, ViewType, isPopupVisible, showScreenPopup, hideScreenPopup, routeOn, routeOff} from 'Knoin/Knoin';
50
import {AbstractViewNext} from 'Knoin/AbstractViewNext';
51
52
@view({
53
	name: 'View/Popup/Compose',
54
	type: ViewType.Popup,
55
	templateID: 'PopupsCompose'
56
})
57
class ComposePopupView extends AbstractViewNext
58
{
59
	constructor() {
60
		super();
61
62
		const
63
			fEmailOutInHelper = (context, identity, name, isIn) => {
64
				if (identity && context && identity[name]() && (isIn ? true : context[name]()))
65
				{
66
					const identityEmail = identity[name]();
67
					let list = trim(context[name]()).split(/[,]/);
68
69
					list = _.filter(list, (email) => {
70
						email = trim(email);
71
						return email && trim(identityEmail) !== email;
72
					});
73
74
					if (isIn)
75
					{
76
						list.push(identityEmail);
77
					}
78
79
					context[name](list.join(','));
80
				}
81
			};
82
83
		this.oLastMessage = null;
84
		this.oEditor = null;
85
		this.aDraftInfo = null;
86
		this.sInReplyTo = '';
87
		this.bFromDraft = false;
88
		this.sReferences = '';
89
90
		this.sLastFocusedField = 'to';
91
92
		this.resizerTrigger = _.bind(this.resizerTrigger, this);
93
94
		this.allowContacts = !!AppStore.contactsIsAllowed();
95
		this.allowFolders = !!Settings.capa(Capa.Folders);
96
97
		this.bSkipNextHide = false;
98
		this.composeInEdit = AppStore.composeInEdit;
99
		this.editorDefaultType = SettingsStore.editorDefaultType;
100
101
		this.capaOpenPGP = PgpStore.capaOpenPGP;
102
103
		this.identitiesDropdownTrigger = ko.observable(false);
104
105
		this.to = ko.observable('');
106
		this.to.focused = ko.observable(false);
107
		this.cc = ko.observable('');
108
		this.cc.focused = ko.observable(false);
109
		this.bcc = ko.observable('');
110
		this.bcc.focused = ko.observable(false);
111
		this.replyTo = ko.observable('');
112
		this.replyTo.focused = ko.observable(false);
113
114
		ko.computed(() => {
115
			switch (true)
116
			{
117
				case this.to.focused():
118
					this.sLastFocusedField = 'to';
119
					break;
120
				case this.cc.focused():
121
					this.sLastFocusedField = 'cc';
122
					break;
123
				case this.bcc.focused():
124
					this.sLastFocusedField = 'bcc';
125
					break;
126
				// no default
127
			}
128
		}).extend({notify: 'always'});
129
130
		this.subject = ko.observable('');
131
		this.subject.focused = ko.observable(false);
132
133
		this.isHtml = ko.observable(false);
134
135
		this.requestDsn = ko.observable(false);
136
		this.requestReadReceipt = ko.observable(false);
137
		this.markAsImportant = ko.observable(false);
138
139
		this.sendError = ko.observable(false);
140
		this.sendSuccessButSaveError = ko.observable(false);
141
		this.savedError = ko.observable(false);
142
143
		this.sendButtonSuccess = ko.computed(
144
			() => !this.sendError() && !this.sendSuccessButSaveError()
145
		);
146
147
		this.sendErrorDesc = ko.observable('');
148
		this.savedErrorDesc = ko.observable('');
149
150
		this.sendError.subscribe((value) => {
151
			if (!value)
152
			{
153
				this.sendErrorDesc('');
154
			}
155
		});
156
157
		this.savedError.subscribe((value) => {
158
			if (!value)
159
			{
160
				this.savedErrorDesc('');
161
			}
162
		});
163
164
		this.sendSuccessButSaveError.subscribe((value) => {
165
			if (!value)
166
			{
167
				this.savedErrorDesc('');
168
			}
169
		});
170
171
		this.savedTime = ko.observable(0);
172
		this.savedTimeText = ko.computed(
173
			() => (0 < this.savedTime() ? i18n('COMPOSE/SAVED_TIME', {'TIME': momentorFormat(this.savedTime() - 1, 'LT')}) : '')
174
		);
175
176
		this.emptyToError = ko.observable(false);
177
		this.emptyToErrorTooltip = ko.computed(
178
			() => (this.emptyToError() ? i18n('COMPOSE/EMPTY_TO_ERROR_DESC') : '')
179
		);
180
181
		this.attachmentsInProcessError = ko.observable(false);
182
		this.attachmentsInErrorError = ko.observable(false);
183
184
		this.attachmentsErrorTooltip = ko.computed(() => {
185
			let result = '';
186
			switch (true)
187
			{
188
				case this.attachmentsInProcessError():
189
					result = i18n('COMPOSE/ATTACHMENTS_UPLOAD_ERROR_DESC');
190
					break;
191
				case this.attachmentsInErrorError():
192
					result = i18n('COMPOSE/ATTACHMENTS_ERROR_DESC');
193
					break;
194
				// no default
195
			}
196
			return result;
197
		});
198
199
		this.showCc = ko.observable(false);
200
		this.showBcc = ko.observable(false);
201
		this.showReplyTo = ko.observable(false);
202
203
		this.cc.subscribe((value) => {
204
			if (false === this.showCc() && 0 < value.length)
205
			{
206
				this.showCc(true);
207
			}
208
		});
209
210
		this.bcc.subscribe((value) => {
211
			if (false === this.showBcc() && 0 < value.length)
212
			{
213
				this.showBcc(true);
214
			}
215
		});
216
217
		this.replyTo.subscribe((value) => {
218
			if (false === this.showReplyTo() && 0 < value.length)
219
			{
220
				this.showReplyTo(true);
221
			}
222
		});
223
224
		this.draftFolder = ko.observable('');
225
		this.draftUid = ko.observable('');
226
		this.sending = ko.observable(false);
227
		this.saving = ko.observable(false);
228
		this.attachments = ko.observableArray([]);
229
230
		this.attachmentsInProcess = this.attachments.filter((item) => item && !item.complete());
231
		this.attachmentsInReady = this.attachments.filter((item) => item && item.complete());
232
		this.attachmentsInError = this.attachments.filter((item) => item && '' !== item.error());
233
234
		this.attachmentsCount = ko.computed(() => this.attachments().length);
235
		this.attachmentsInErrorCount = ko.computed(() => this.attachmentsInError().length);
236
		this.attachmentsInProcessCount = ko.computed(() => this.attachmentsInProcess().length);
237
		this.isDraftFolderMessage = ko.computed(() => '' !== this.draftFolder() && '' !== this.draftUid());
238
239
		this.attachmentsPlace = ko.observable(false);
240
241
		this.attachments.subscribe(this.resizerTrigger);
242
		this.attachmentsPlace.subscribe(this.resizerTrigger);
243
244
		this.attachmentsInErrorCount.subscribe((value) => {
245
			if (0 === value)
246
			{
247
				this.attachmentsInErrorError(false);
248
			}
249
		});
250
251
		this.composeUploaderButton = ko.observable(null);
252
		this.composeUploaderDropPlace = ko.observable(null);
253
		this.dragAndDropEnabled = ko.observable(false);
254
		this.dragAndDropOver = ko.observable(false).extend({throttle: 1});
255
		this.dragAndDropVisible = ko.observable(false).extend({throttle: 1});
256
		this.attacheMultipleAllowed = ko.observable(false);
257
		this.addAttachmentEnabled = ko.observable(false);
258
259
		this.composeEditorArea = ko.observable(null);
260
261
		this.identities = IdentityStore.identities;
262
		this.identitiesOptions = ko.computed(
263
			() => _.map(IdentityStore.identities(), (item) => ({
264
				'item': item,
265
				'optValue': item.id(),
266
				'optText': item.formattedName()
267
			}))
268
		);
269
270
		this.currentIdentity = ko.observable(
271
			this.identities()[0] ? this.identities()[0] : null);
272
273
		this.currentIdentity.extend({toggleSubscribe: [
274
			this,
275
			(identity) => {
276
				fEmailOutInHelper(this, identity, 'bcc');
277
				fEmailOutInHelper(this, identity, 'replyTo');
278
			},
279
			(identity) => {
280
				fEmailOutInHelper(this, identity, 'bcc', true);
281
				fEmailOutInHelper(this, identity, 'replyTo', true);
282
			}
283
		]});
284
285
		this.currentIdentityView = ko.computed(() => {
286
			const item = this.currentIdentity();
287
			return item ? item.formattedName() : 'unknown';
288
		});
289
290
		this.to.subscribe((value) => {
291
			if (this.emptyToError() && 0 < value.length)
292
			{
293
				this.emptyToError(false);
294
			}
295
		});
296
297
		this.attachmentsInProcess.subscribe((value) => {
298
			if (this.attachmentsInProcessError() && isArray(value) && 0 === value.length)
299
			{
300
				this.attachmentsInProcessError(false);
301
			}
302
		});
303
304
		this.resizer = ko.observable(false).extend({throttle: 50});
305
306
		this.resizer.subscribe(() => {
307
			if (this.oEditor) {
308
				this.oEditor.resize();
309
			}
310
		});
311
312
		this.canBeSentOrSaved = ko.computed(() => !this.sending() && !this.saving());
313
314
		this.deleteCommand = createCommand(() => {
315
			const PopupsAskViewModel = require('View/Popup/Ask');
316
			if (!isPopupVisible(PopupsAskViewModel) && this.modalVisibility())
317
			{
318
				showScreenPopup(PopupsAskViewModel, [i18n('POPUPS_ASK/DESC_WANT_DELETE_MESSAGES'), () => {
319
					if (this.modalVisibility())
320
					{
321
						getApp().deleteMessagesFromFolderWithoutCheck(this.draftFolder(), [this.draftUid()]);
322
						hideScreenPopup(ComposePopupView);
323
					}
324
				}]);
325
			}
326
327
		}, () => this.isDraftFolderMessage());
328
329
		this.sendMessageResponse = _.bind(this.sendMessageResponse, this);
330
		this.saveMessageResponse = _.bind(this.saveMessageResponse, this);
331
332
		this.sendCommand = createCommand(() => {
333
334
			const
335
				sTo = trim(this.to()),
336
				sCc = trim(this.cc()),
337
				sBcc = trim(this.bcc());
338
			let
339
				sSentFolder = FolderStore.sentFolder();
340
341
			this.attachmentsInProcessError(false);
342
			this.attachmentsInErrorError(false);
343
			this.emptyToError(false);
344
345
			if (0 < this.attachmentsInProcess().length)
346
			{
347
				this.attachmentsInProcessError(true);
348
				this.attachmentsPlace(true);
349
			}
350
			else if (0 < this.attachmentsInError().length)
351
			{
352
				this.attachmentsInErrorError(true);
353
				this.attachmentsPlace(true);
354
			}
355
356
			if ('' === sTo && '' === sCc && '' === sBcc)
357
			{
358
				this.emptyToError(true);
359
			}
360
361
			if (!this.emptyToError() && !this.attachmentsInErrorError() && !this.attachmentsInProcessError())
362
			{
363
				if (SettingsStore.replySameFolder())
364
				{
365
					if (isArray(this.aDraftInfo) && 3 === this.aDraftInfo.length && isNormal(this.aDraftInfo[2]) && 0 < this.aDraftInfo[2].length)
366
					{
367
						sSentFolder = this.aDraftInfo[2];
368
					}
369
				}
370
371
				if (!this.allowFolders)
372
				{
373
					sSentFolder = UNUSED_OPTION_VALUE;
374
				}
375
376
				if ('' === sSentFolder)
377
				{
378
					showScreenPopup(require('View/Popup/FolderSystem'), [SetSystemFoldersNotification.Sent]);
379
				}
380
				else
381
				{
382
					this.sendError(false);
383
					this.sending(true);
384
385
					if (isArray(this.aDraftInfo) && 3 === this.aDraftInfo.length)
386
					{
387
						const flagsCache = getMessageFlagsFromCache(this.aDraftInfo[2], this.aDraftInfo[1]);
388
						if (flagsCache)
389
						{
390
							if ('forward' === this.aDraftInfo[0])
391
							{
392
								flagsCache[3] = true;
393
							}
394
							else
395
							{
396
								flagsCache[2] = true;
397
							}
398
399
							setMessageFlagsToCache(this.aDraftInfo[2], this.aDraftInfo[1], flagsCache);
400
							getApp().reloadFlagsCurrentMessageListAndMessageFromCache();
401
							setFolderHash(this.aDraftInfo[2], '');
402
						}
403
					}
404
405
					sSentFolder = UNUSED_OPTION_VALUE === sSentFolder ? '' : sSentFolder;
406
407
					setFolderHash(this.draftFolder(), '');
408
					setFolderHash(sSentFolder, '');
409
410
					Remote.sendMessage(
411
						this.sendMessageResponse,
412
						this.currentIdentity() ? this.currentIdentity().id() : '',
413
						this.draftFolder(),
414
						this.draftUid(),
415
						sSentFolder,
416
						sTo,
417
						this.cc(),
418
						this.bcc(),
419
						this.replyTo(),
420
						this.subject(),
421
						this.oEditor ? this.oEditor.isHtml() : false,
422
						this.oEditor ? this.oEditor.getData(true) : '',
423
						this.prepearAttachmentsForSendOrSave(),
424
						this.aDraftInfo,
425
						this.sInReplyTo,
426
						this.sReferences,
427
						this.requestDsn(),
428
						this.requestReadReceipt(),
429
						this.markAsImportant()
430
					);
431
				}
432
			}
433
434
		}, this.canBeSentOrSaved);
435
436
		this.saveCommand = createCommand(() => {
437
438
			if (!this.allowFolders)
439
			{
440
				return false;
441
			}
442
443
			if (FolderStore.draftFolderNotEnabled())
444
			{
445
				showScreenPopup(require('View/Popup/FolderSystem'), [SetSystemFoldersNotification.Draft]);
446
			}
447
			else
448
			{
449
				this.savedError(false);
450
				this.saving(true);
451
452
				this.autosaveStart();
453
454
				setFolderHash(FolderStore.draftFolder(), '');
455
456
				Remote.saveMessage(
457
					this.saveMessageResponse,
458
					this.currentIdentity() ? this.currentIdentity().id() : '',
459
					this.draftFolder(),
460
					this.draftUid(),
461
					FolderStore.draftFolder(),
462
					this.to(),
463
					this.cc(),
464
					this.bcc(),
465
					this.replyTo(),
466
					this.subject(),
467
					this.oEditor ? this.oEditor.isHtml() : false,
468
					this.oEditor ? this.oEditor.getData(true) : '',
469
					this.prepearAttachmentsForSendOrSave(),
470
					this.aDraftInfo,
471
					this.sInReplyTo,
472
					this.sReferences,
473
					this.markAsImportant()
474
				);
475
			}
476
477
			return true;
478
479
		}, this.canBeSentOrSaved);
480
481
		this.skipCommand = createCommand(() => {
482
483
			this.bSkipNextHide = true;
484
485
			if (this.modalVisibility() && !this.saving() && !this.sending() &&
486
				!FolderStore.draftFolderNotEnabled())
487
			{
488
				this.saveCommand();
489
			}
490
491
			this.tryToClosePopup();
492
493
		}, this.canBeSentOrSaved);
494
495
		this.contactsCommand = createCommand(() => {
496
497
			if (this.allowContacts)
498
			{
499
				this.skipCommand();
500
				_.delay(() => {
501
					showScreenPopup(require('View/Popup/Contacts'), [true, this.sLastFocusedField]);
502
				}, Magics.Time200ms);
503
			}
504
505
		}, () => this.allowContacts);
506
507
		Events.sub('interval.2m', () => {
508
			if (this.modalVisibility() && !FolderStore.draftFolderNotEnabled() && !this.isEmptyForm(false) &&
509
				!this.saving() && !this.sending() && !this.savedError())
510
			{
511
				this.saveCommand();
512
			}
513
		});
514
515
		this.showCc.subscribe(this.resizerTrigger);
516
		this.showBcc.subscribe(this.resizerTrigger);
517
		this.showReplyTo.subscribe(this.resizerTrigger);
518
519
		this.dropboxEnabled = SocialStore.dropbox.enabled;
520
		this.dropboxApiKey = SocialStore.dropbox.apiKey;
521
522
		this.dropboxCommand = createCommand(() => {
523
524
			if (window.Dropbox)
525
			{
526
				window.Dropbox.choose({
527
					success: (files) => {
528
						if (files && files[0] && files[0].link)
529
						{
530
							this.addDropboxAttachment(files[0]);
531
						}
532
					},
533
					linkType: 'direct',
534
					multiselect: false
535
				});
536
			}
537
538
			return true;
539
540
		}, () => this.dropboxEnabled());
541
542
		this.driveEnabled = ko.observable(bXMLHttpRequestSupported &&
543
			!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialDrive') &&
544
			!!Settings.settingsGet('GoogleClientID') && !!Settings.settingsGet('GoogleApiKey'));
545
546
		this.driveVisible = ko.observable(false);
547
548
		this.driveCommand = createCommand(() => {
549
			this.driveOpenPopup();
550
			return true;
551
		}, () => this.driveEnabled());
552
553
		this.driveCallback = _.bind(this.driveCallback, this);
554
555
		this.onMessageUploadAttachments = _.bind(this.onMessageUploadAttachments, this);
556
557
		this.bDisabeCloseOnEsc = true;
558
		this.sDefaultKeyScope = KeyState.Compose;
559
560
		this.tryToClosePopup = _.debounce(_.bind(this.tryToClosePopup, this), Magics.Time200ms);
561
562
		this.emailsSource = _.bind(this.emailsSource, this);
563
		this.autosaveFunction = _.bind(this.autosaveFunction, this);
564
565
		this.iTimer = 0;
566
	}
567
568
	autosaveFunction() {
569
		if (this.modalVisibility() && !FolderStore.draftFolderNotEnabled() && !this.isEmptyForm(false) &&
570
			!this.saving() && !this.sending() && !this.savedError())
571
		{
572
			this.saveCommand();
573
		}
574
575
		this.autosaveStart();
576
	}
577
578
	autosaveStart() {
579
		window.clearTimeout(this.iTimer);
580
		this.iTimer = window.setTimeout(this.autosaveFunction, Magics.Time1m);
581
	}
582
583
	autosaveStop() {
584
		window.clearTimeout(this.iTimer);
585
	}
586
587
	emailsSource(oData, fResponse) {
588
		getApp().getAutocomplete(oData.term, (aData) => {
589
			fResponse(_.map(aData, (oEmailItem) => oEmailItem.toLine(false)));
590
		});
591
	}
592
593
	openOpenPgpPopup() {
594
		if (PgpStore.capaOpenPGP() && this.oEditor && !this.oEditor.isHtml())
595
		{
596
			showScreenPopup(require('View/Popup/ComposeOpenPgp'), [
597
				(result) => {
598
					this.editor((editor) => {
599
						editor.setPlain(result);
600
					});
601
				},
602
				this.oEditor.getData(false),
603
				this.currentIdentity(),
604
				this.to(),
605
				this.cc(),
606
				this.bcc()
607
			]);
608
		}
609
	}
610
611
	reloadDraftFolder() {
612
		const draftFolder = FolderStore.draftFolder();
613
		if ('' !== draftFolder && UNUSED_OPTION_VALUE !== draftFolder)
614
		{
615
			setFolderHash(draftFolder, '');
616
			if (FolderStore.currentFolderFullNameRaw() === draftFolder)
617
			{
618
				getApp().reloadMessageList(true);
619
			}
620
			else
621
			{
622
				getApp().folderInformation(draftFolder);
623
			}
624
		}
625
	}
626
627
	findIdentityByMessage(composeType, message) {
628
629
		let
630
			resultIndex = 1000,
631
			resultIdentity = null;
632
		const
633
			identities = IdentityStore.identities(),
634
			identitiesCache = {},
635
			fEachHelper = (item) => {
636
				if (item && item.email && identitiesCache[item.email])
637
				{
638
					if (!resultIdentity || resultIndex > identitiesCache[item.email][1])
639
					{
640
						resultIdentity = identitiesCache[item.email][0];
641
						resultIndex = identitiesCache[item.email][1];
642
					}
643
				}
644
			};
645
646
		_.each(identities, (item, index) => {
647
			identitiesCache[item.email()] = [item, index];
648
		});
649
650
		if (message)
651
		{
652
			switch (composeType)
653
			{
654
				case ComposeType.Empty:
655
					break;
656
				case ComposeType.Reply:
657
				case ComposeType.ReplyAll:
658
				case ComposeType.Forward:
659
				case ComposeType.ForwardAsAttachment:
660
					_.each(_.union(message.to, message.cc, message.bcc), fEachHelper);
661
					if (!resultIdentity) {
662
						_.each(message.deliveredTo, fEachHelper);
663
					}
664
					break;
665
				case ComposeType.Draft:
666
					_.each(_.union(message.from, message.replyTo), fEachHelper);
667
					break;
668
				// no default
669
			}
670
		}
671
672
		return resultIdentity || identities[0] || null;
673
	}
674
675
	selectIdentity(identity) {
676
		if (identity && identity.item)
677
		{
678
			this.currentIdentity(identity.item);
679
			this.setSignatureFromIdentity(identity.item);
680
		}
681
	}
682
683
	sendMessageResponse(statusResult, data) {
684
		let
685
			result = false,
686
			message = '';
687
688
		this.sending(false);
689
690
		if (StorageResultType.Success === statusResult && data && data.Result)
691
		{
692
			result = true;
693
			if (this.modalVisibility())
694
			{
695
				delegateRun(this, 'closeCommand');
696
			}
697
		}
698
699
		if (this.modalVisibility() && !result)
700
		{
701
			if (data && Notification.CantSaveMessage === data.ErrorCode)
702
			{
703
				this.sendSuccessButSaveError(true);
704
				this.savedErrorDesc(trim(i18n('COMPOSE/SAVED_ERROR_ON_SEND')));
705
			}
706
			else
707
			{
708
				message = getNotification(data && data.ErrorCode ? data.ErrorCode : Notification.CantSendMessage,
709
					data && data.ErrorMessage ? data.ErrorMessage : '');
710
711
				this.sendError(true);
712
				this.sendErrorDesc(message || getNotification(Notification.CantSendMessage));
713
			}
714
		}
715
716
		this.reloadDraftFolder();
717
	}
718
719
	saveMessageResponse(statusResult, oData) {
720
721
		let result = false;
722
723
		this.saving(false);
724
725
		if (StorageResultType.Success === statusResult && oData && oData.Result)
726
		{
727
			if (oData.Result.NewFolder && oData.Result.NewUid)
728
			{
729
				result = true;
730
731
				if (this.bFromDraft)
732
				{
733
					const message = MessageStore.message();
734
					if (message && this.draftFolder() === message.folderFullNameRaw && this.draftUid() === message.uid)
735
					{
736
						MessageStore.message(null);
737
					}
738
				}
739
740
				this.draftFolder(oData.Result.NewFolder);
741
				this.draftUid(oData.Result.NewUid);
742
743
				this.savedTime(window.Math.round((new window.Date()).getTime() / 1000));
744
745
				if (this.bFromDraft)
746
				{
747
					setFolderHash(this.draftFolder(), '');
748
				}
749
			}
750
		}
751
752
		if (!result)
753
		{
754
			this.savedError(true);
755
			this.savedErrorDesc(getNotification(Notification.CantSaveMessage));
756
		}
757
758
		this.reloadDraftFolder();
759
	}
760
761
	onHide() {
762
		this.autosaveStop();
763
764
		if (!this.bSkipNextHide)
765
		{
766
			AppStore.composeInEdit(false);
767
			this.reset();
768
		}
769
770
		this.bSkipNextHide = false;
771
772
		this.to.focused(false);
773
774
		routeOn();
775
	}
776
777
	editor(fOnInit) {
778
		if (fOnInit)
779
		{
780
			if (!this.oEditor && this.composeEditorArea())
781
			{
782
// _.delay(() => {
783
				this.oEditor = new HtmlEditor(this.composeEditorArea(), null, () => {
784
					fOnInit(this.oEditor);
785
					this.resizerTrigger();
786
				}, (bHtml) => {
787
					this.isHtml(!!bHtml);
788
				});
789
// }, 1000);
790
			}
791
			else if (this.oEditor)
792
			{
793
				fOnInit(this.oEditor);
794
				this.resizerTrigger();
795
			}
796
		}
797
	}
798
799
	converSignature(signature) {
800
		let
801
			limit = 10,
802
			fromLine = '';
803
804
		const
805
			moments = [],
806
			momentRegx = /{{MOMENT:([^}]+)}}/g;
807
808
		signature = signature.replace(/[\r]/g, '');
809
810
		fromLine = this.oLastMessage ? this.emailArrayToStringLineHelper(this.oLastMessage.from, true) : '';
811
		if ('' !== fromLine)
812
		{
813
			signature = signature.replace(/{{FROM-FULL}}/g, fromLine);
814
815
			if (-1 === fromLine.indexOf(' ') && 0 < fromLine.indexOf('@'))
816
			{
817
				fromLine = fromLine.replace(/@[\S]+/, '');
818
			}
819
820
			signature = signature.replace(/{{FROM}}/g, fromLine);
821
		}
822
823
		signature = signature.replace(/[\s]{1,2}{{FROM}}/g, '{{FROM}}');
824
		signature = signature.replace(/[\s]{1,2}{{FROM-FULL}}/g, '{{FROM-FULL}}');
825
826
		signature = signature.replace(/{{FROM}}/g, '');
827
		signature = signature.replace(/{{FROM-FULL}}/g, '');
828
829
		if (-1 < signature.indexOf('{{DATE}}'))
830
		{
831
			signature = signature.replace(/{{DATE}}/g, momentorFormat(0, 'llll'));
832
		}
833
834
		if (-1 < signature.indexOf('{{TIME}}'))
835
		{
836
			signature = signature.replace(/{{TIME}}/g, momentorFormat(0, 'LT'));
837
		}
838
		if (-1 < signature.indexOf('{{MOMENT:'))
839
		{
840
			try
841
			{
842
				let match = null;
843
				while (null !== (match = momentRegx.exec(signature))) // eslint-disable-line no-cond-assign
844
				{
845
					if (match && match[0] && match[1])
846
					{
847
						moments.push([match[0], match[1]]);
848
					}
849
850
					limit -= 1;
851
					if (0 === limit)
852
					{
853
						break;
854
					}
855
				}
856
857
				if (moments && 0 < moments.length)
858
				{
859
					_.each(moments, (data) => {
860
						signature = signature.replace(data[0], momentorFormat(0, data[1]));
861
					});
862
				}
863
864
				signature = signature.replace(/{{MOMENT:[^}]+}}/g, '');
865
			}
866
			catch (e) {} // eslint-disable-line no-empty
867
		}
868
869
		return signature;
870
	}
871
872
	setSignatureFromIdentity(oIdentity) {
873
		if (oIdentity)
874
		{
875
			this.editor((editor) => {
876
				let
877
					isHtml = false,
878
					signature = oIdentity.signature();
879
880
				if ('' !== signature)
881
				{
882
					if (':HTML:' === signature.substr(0, 6))
883
					{
884
						isHtml = true;
885
						signature = signature.substr(6);
886
					}
887
				}
888
889
				editor.setSignature(this.converSignature(signature), isHtml, !!oIdentity.signatureInsertBefore());
890
			});
891
		}
892
	}
893
894
	/**
895
	 * @param {string=} type = ComposeType.Empty
896
	 * @param {?MessageModel|Array=} oMessageOrArray = null
897
	 * @param {Array=} aToEmails = null
898
	 * @param {Array=} aCcEmails = null
899
	 * @param {Array=} aBccEmails = null
900
	 * @param {string=} sCustomSubject = null
901
	 * @param {string=} sCustomPlainText = null
902
	 */
903
	onShow(type, oMessageOrArray, aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText) {
904
905
		routeOff();
906
907
		this.autosaveStart();
908
909
		if (AppStore.composeInEdit())
910
		{
911
			type = type || ComposeType.Empty;
912
			if (ComposeType.Empty !== type)
913
			{
914
				showScreenPopup(require('View/Popup/Ask'), [i18n('COMPOSE/DISCARD_UNSAVED_DATA'), () => {
915
					this.initOnShow(type, oMessageOrArray, aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText);
916
				}, null, null, null, false]);
917
			}
918
			else
919
			{
920
				this.addEmailsTo(this.to, aToEmails);
921
				this.addEmailsTo(this.cc, aCcEmails);
922
				this.addEmailsTo(this.bcc, aBccEmails);
923
924
				if (isNormal(sCustomSubject) && '' !== sCustomSubject && '' === this.subject())
925
				{
926
					this.subject(sCustomSubject);
927
				}
928
			}
929
		}
930
		else
931
		{
932
			this.initOnShow(type, oMessageOrArray, aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText);
933
		}
934
	}
935
936
	onWarmUp() {
937
		if (this.modalVisibility && !this.modalVisibility())
938
		{
939
			this.editor((editor) => editor.modeToggle(false));
940
		}
941
	}
942
943
	/**
944
	 * @param {Function} fKoValue
945
	 * @param {Array} emails
946
	 */
947
	addEmailsTo(fKoValue, emails) {
948
		if (isNonEmptyArray(emails))
949
		{
950
			const
951
				value = trim(fKoValue()),
952
				values = _.uniq(_.compact(_.map(emails, (item) => (item ? item.toLine(false) : null))));
953
954
			fKoValue(value + ('' === value ? '' : ', ') + trim(values.join(', ')));
955
		}
956
	}
957
958
	/**
959
	 *
960
	 * @param {Array} aList
961
	 * @param {boolean} bFriendly
962
	 * @returns {string}
963
	 */
964
	emailArrayToStringLineHelper(aList, bFriendly) {
965
		bFriendly = !!bFriendly;
966
		return _.map(aList, (item) => item.toLine(bFriendly)).join(', ');
967
	}
968
969
	/**
970
	 * @param {string=} sType = ComposeType.Empty
971
	 * @param {?MessageModel|Array=} oMessageOrArray = null
972
	 * @param {Array=} aToEmails = null
973
	 * @param {Array=} aCcEmails = null
974
	 * @param {Array=} aBccEmails = null
975
	 * @param {string=} sCustomSubject = null
976
	 * @param {string=} sCustomPlainText = null
977
	 */
978
	initOnShow(sType, oMessageOrArray, aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText)
979
	{
980
		AppStore.composeInEdit(true);
981
982
		let
983
			sFrom = '',
984
			sTo = '',
985
			sCc = '',
986
			sDate = '',
987
			sSubject = '',
988
			sText = '',
989
			sReplyTitle = '',
990
			identity = null,
991
			aDraftInfo = null,
992
			message = null;
993
994
		const
995
			excludeEmail = {},
996
			mEmail = AccountStore.email(),
997
			lineComposeType = sType || ComposeType.Empty;
998
999
		oMessageOrArray = oMessageOrArray || null;
1000
		if (oMessageOrArray && isNormal(oMessageOrArray))
1001
		{
1002
			message = isArray(oMessageOrArray) &&
1003
				1 === oMessageOrArray.length ? oMessageOrArray[0] :
1004
					(!isArray(oMessageOrArray) ? oMessageOrArray : null);
1005
		}
1006
1007
		this.oLastMessage = message;
1008
1009
		if (null !== mEmail)
1010
		{
1011
			excludeEmail[mEmail] = true;
1012
		}
1013
1014
		this.reset();
1015
1016
		identity = this.findIdentityByMessage(lineComposeType, message);
1017
		if (identity)
1018
		{
1019
			excludeEmail[identity.email()] = true;
1020
		}
1021
1022
		if (isNonEmptyArray(aToEmails))
1023
		{
1024
			this.to(this.emailArrayToStringLineHelper(aToEmails));
1025
		}
1026
1027
		if (isNonEmptyArray(aCcEmails))
1028
		{
1029
			this.cc(this.emailArrayToStringLineHelper(aCcEmails));
1030
		}
1031
1032
		if (isNonEmptyArray(aBccEmails))
1033
		{
1034
			this.bcc(this.emailArrayToStringLineHelper(aBccEmails));
1035
		}
1036
1037
		if ('' !== lineComposeType && message)
1038
		{
1039
			sDate = momentorFormat(message.dateTimeStampInUTC(), 'FULL');
1040
			sSubject = message.subject();
1041
			aDraftInfo = message.aDraftInfo;
1042
1043
			const clonedText = $(message.body).clone();
1044
			if (clonedText)
1045
			{
1046
				clearBqSwitcher(clonedText);
1047
1048
				sText = clonedText.html();
1049
			}
1050
1051
			let resplyAllParts = null;
1052
			switch (lineComposeType)
1053
			{
1054
				case ComposeType.Empty:
1055
					break;
1056
1057
				case ComposeType.Reply:
1058
					this.to(this.emailArrayToStringLineHelper(message.replyEmails(excludeEmail)));
1059
					this.subject(replySubjectAdd('Re', sSubject));
1060
					this.prepearMessageAttachments(message, lineComposeType);
1061
					this.aDraftInfo = ['reply', message.uid, message.folderFullNameRaw];
1062
					this.sInReplyTo = message.sMessageId;
1063
					this.sReferences = trim(this.sInReplyTo + ' ' + message.sReferences);
1064
					break;
1065
1066
				case ComposeType.ReplyAll:
1067
					resplyAllParts = message.replyAllEmails(excludeEmail);
1068
					this.to(this.emailArrayToStringLineHelper(resplyAllParts[0]));
1069
					this.cc(this.emailArrayToStringLineHelper(resplyAllParts[1]));
1070
					this.subject(replySubjectAdd('Re', sSubject));
1071
					this.prepearMessageAttachments(message, lineComposeType);
1072
					this.aDraftInfo = ['reply', message.uid, message.folderFullNameRaw];
1073
					this.sInReplyTo = message.sMessageId;
1074
					this.sReferences = trim(this.sInReplyTo + ' ' + message.references());
1075
					break;
1076
1077
				case ComposeType.Forward:
1078
					this.subject(replySubjectAdd('Fwd', sSubject));
1079
					this.prepearMessageAttachments(message, lineComposeType);
1080
					this.aDraftInfo = ['forward', message.uid, message.folderFullNameRaw];
1081
					this.sInReplyTo = message.sMessageId;
1082
					this.sReferences = trim(this.sInReplyTo + ' ' + message.sReferences);
1083
					break;
1084
1085
				case ComposeType.ForwardAsAttachment:
1086
					this.subject(replySubjectAdd('Fwd', sSubject));
1087
					this.prepearMessageAttachments(message, lineComposeType);
1088
					this.aDraftInfo = ['forward', message.uid, message.folderFullNameRaw];
1089
					this.sInReplyTo = message.sMessageId;
1090
					this.sReferences = trim(this.sInReplyTo + ' ' + message.sReferences);
1091
					break;
1092
1093
				case ComposeType.Draft:
1094
					this.to(this.emailArrayToStringLineHelper(message.to));
1095
					this.cc(this.emailArrayToStringLineHelper(message.cc));
1096
					this.bcc(this.emailArrayToStringLineHelper(message.bcc));
1097
					this.replyTo(this.emailArrayToStringLineHelper(message.replyTo));
1098
1099
					this.bFromDraft = true;
1100
1101
					this.draftFolder(message.folderFullNameRaw);
1102
					this.draftUid(message.uid);
1103
1104
					this.subject(sSubject);
1105
					this.prepearMessageAttachments(message, lineComposeType);
1106
1107
					this.aDraftInfo = isNonEmptyArray(aDraftInfo) && 3 === aDraftInfo.length ? aDraftInfo : null;
1108
					this.sInReplyTo = message.sInReplyTo;
1109
					this.sReferences = message.sReferences;
1110
					break;
1111
1112
				case ComposeType.EditAsNew:
1113
					this.to(this.emailArrayToStringLineHelper(message.to));
1114
					this.cc(this.emailArrayToStringLineHelper(message.cc));
1115
					this.bcc(this.emailArrayToStringLineHelper(message.bcc));
1116
					this.replyTo(this.emailArrayToStringLineHelper(message.replyTo));
1117
1118
					this.subject(sSubject);
1119
					this.prepearMessageAttachments(message, lineComposeType);
1120
1121
					this.aDraftInfo = isNonEmptyArray(aDraftInfo) && 3 === aDraftInfo.length ? aDraftInfo : null;
1122
					this.sInReplyTo = message.sInReplyTo;
1123
					this.sReferences = message.sReferences;
1124
					break;
1125
				// no default
1126
			}
1127
1128
			switch (lineComposeType)
1129
			{
1130
				case ComposeType.Reply:
1131
				case ComposeType.ReplyAll:
1132
					sFrom = message.fromToLine(false, true);
1133
					sReplyTitle = i18n('COMPOSE/REPLY_MESSAGE_TITLE', {
1134
						'DATETIME': sDate,
1135
						'EMAIL': sFrom
1136
					});
1137
1138
					sText = '<br /><br />' + sReplyTitle + ':' +
1139
						'<blockquote>' + trim(sText) + '</blockquote>';
1140
	//						'<blockquote><p>' + trim(sText) + '</p></blockquote>';
1141
1142
					break;
1143
1144
				case ComposeType.Forward:
1145
					sFrom = message.fromToLine(false, true);
1146
					sTo = message.toToLine(false, true);
1147
					sCc = message.ccToLine(false, true);
1148
					sText = '<br /><br />' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') +
1149
							'<br />' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_FROM') + ': ' + sFrom +
1150
							'<br />' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_TO') + ': ' + sTo +
1151
							(0 < sCc.length ? '<br />' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_CC') + ': ' + sCc : '') +
1152
							'<br />' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT') + ': ' + encodeHtml(sDate) +
1153
							'<br />' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_SUBJECT') + ': ' + encodeHtml(sSubject) +
1154
							'<br /><br />' + trim(sText) + '<br /><br />';
1155
					break;
1156
1157
				case ComposeType.ForwardAsAttachment:
1158
					sText = '';
1159
					break;
1160
				// no default
1161
			}
1162
1163
			this.editor((editor) => {
1164
1165
				editor.setHtml(sText, false);
1166
1167
				if (EditorDefaultType.PlainForced === this.editorDefaultType() ||
1168
					(!message.isHtml() && EditorDefaultType.HtmlForced !== this.editorDefaultType()))
1169
				{
1170
					editor.modeToggle(false);
1171
				}
1172
1173
				if (identity && ComposeType.Draft !== lineComposeType && ComposeType.EditAsNew !== lineComposeType)
1174
				{
1175
					this.setSignatureFromIdentity(identity);
1176
				}
1177
1178
				this.setFocusInPopup();
1179
			});
1180
		}
1181
		else if (ComposeType.Empty === lineComposeType)
1182
		{
1183
			this.subject(isNormal(sCustomSubject) ? '' + sCustomSubject : '');
1184
1185
			sText = isNormal(sCustomPlainText) ? '' + sCustomPlainText : '';
1186
1187
			this.editor((editor) => {
1188
1189
				editor.setHtml(sText, false);
1190
1191
				if (EditorDefaultType.Html !== this.editorDefaultType() &&
1192
					EditorDefaultType.HtmlForced !== this.editorDefaultType())
1193
				{
1194
					editor.modeToggle(false);
1195
				}
1196
1197
				if (identity)
1198
				{
1199
					this.setSignatureFromIdentity(identity);
1200
				}
1201
1202
				this.setFocusInPopup();
1203
			});
1204
		}
1205
		else if (isNonEmptyArray(oMessageOrArray))
1206
		{
1207
			_.each(oMessageOrArray, (item) => {
1208
				this.addMessageAsAttachment(item);
1209
			});
1210
1211
			this.editor((editor) => {
1212
1213
				editor.setHtml('', false);
1214
1215
				if (EditorDefaultType.Html !== this.editorDefaultType() &&
1216
					EditorDefaultType.HtmlForced !== this.editorDefaultType())
1217
				{
1218
					editor.modeToggle(false);
1219
				}
1220
1221
				if (identity && ComposeType.Draft !== lineComposeType && ComposeType.EditAsNew !== lineComposeType)
1222
				{
1223
					this.setSignatureFromIdentity(identity);
1224
				}
1225
1226
				this.setFocusInPopup();
1227
			});
1228
		}
1229
		else
1230
		{
1231
			this.setFocusInPopup();
1232
		}
1233
1234
		const downloads = this.getAttachmentsDownloadsForUpload();
1235
		if (isNonEmptyArray(downloads))
1236
		{
1237
			Remote.messageUploadAttachments(this.onMessageUploadAttachments, downloads);
1238
		}
1239
1240
		if (identity)
1241
		{
1242
			this.currentIdentity(identity);
1243
		}
1244
1245
		this.resizerTrigger();
1246
	}
1247
1248
	onMessageUploadAttachments(sResult, oData) {
1249
		if (StorageResultType.Success === sResult && oData && oData.Result)
1250
		{
1251
			if (!this.viewModelVisibility())
1252
			{
1253
				_.each(oData.Result, (id, tempName) => {
1254
					const attachment = this.getAttachmentById(id);
1255
					if (attachment)
1256
					{
1257
						attachment.tempName(tempName);
1258
						attachment.waiting(false).uploading(false).complete(true);
1259
					}
1260
				});
1261
			}
1262
		}
1263
		else
1264
		{
1265
			this.setMessageAttachmentFailedDownloadText();
1266
		}
1267
	}
1268
1269
	setFocusInPopup() {
1270
		if (!bMobileDevice)
1271
		{
1272
			_.delay(() => {
1273
1274
				if ('' === this.to())
1275
				{
1276
					this.to.focused(true);
1277
				}
1278
				else if (this.oEditor)
1279
				{
1280
					if (!this.to.focused())
1281
					{
1282
						this.oEditor.focus();
1283
					}
1284
				}
1285
1286
			}, Magics.Time100ms);
1287
		}
1288
	}
1289
1290
	onShowWithDelay() {
1291
		this.resizerTrigger();
1292
	}
1293
1294
	tryToClosePopup() {
1295
		const PopupsAskViewModel = require('View/Popup/Ask');
1296
		if (!isPopupVisible(PopupsAskViewModel) && this.modalVisibility())
1297
		{
1298
			if (this.bSkipNextHide || (this.isEmptyForm() && !this.draftUid()))
1299
			{
1300
				delegateRun(this, 'closeCommand');
1301
			}
1302
			else
1303
			{
1304
				showScreenPopup(PopupsAskViewModel, [i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'), () => {
1305
					if (this.modalVisibility())
1306
					{
1307
						delegateRun(this, 'closeCommand');
1308
					}
1309
				}]);
1310
			}
1311
		}
1312
	}
1313
1314
	onBuild() {
1315
		this.initUploader();
1316
1317
		key('ctrl+q, command+q, ctrl+w, command+w', KeyState.Compose, noopFalse);
1318
1319
		key('`', KeyState.Compose, () => {
1320
			if (this.oEditor && !this.oEditor.hasFocus() && !inFocus())
1321
			{
1322
				this.identitiesDropdownTrigger(true);
1323
				return false;
1324
			}
1325
1326
			return true;
1327
		});
1328
1329
		key('ctrl+`', KeyState.Compose, () => {
1330
			this.identitiesDropdownTrigger(true);
1331
			return false;
1332
		});
1333
1334
		key('esc, ctrl+down, command+down', KeyState.Compose, () => {
1335
			this.skipCommand();
1336
			return false;
1337
		});
1338
1339
		if (this.allowFolders)
1340
		{
1341
			key('ctrl+s, command+s', KeyState.Compose, () => {
1342
				this.saveCommand();
1343
				return false;
1344
			});
1345
		}
1346
1347
		if (Settings.appSettingsGet('allowCtrlEnterOnCompose'))
1348
		{
1349
			key('ctrl+enter, command+enter', KeyState.Compose, () => {
1350
				this.sendCommand();
1351
				return false;
1352
			});
1353
		}
1354
1355
		key('shift+esc', KeyState.Compose, () => {
1356
			if (this.modalVisibility())
1357
			{
1358
				this.tryToClosePopup();
1359
			}
1360
			return false;
1361
		});
1362
1363
		Events.sub('window.resize.real', this.resizerTrigger);
1364
		Events.sub('window.resize.real', _.debounce(this.resizerTrigger, Magics.Time50ms));
1365
1366
		SocialStore.appendDropbox();
1367
1368
		if (this.driveEnabled())
1369
		{
1370
			$.getScript('https://apis.google.com/js/api.js', () => {
1371
				if (window.gapi)
1372
				{
1373
					this.driveVisible(true);
1374
				}
1375
			});
1376
		}
1377
1378
		window.setInterval(() => {
1379
			if (this.modalVisibility() && this.oEditor)
1380
			{
1381
				this.oEditor.resize();
1382
			}
1383
		}, Magics.Time5s);
1384
	}
1385
1386
	driveCallback(accessToken, data) {
1387
		if (data && window.XMLHttpRequest && window.google &&
1388
			data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED &&
1389
			data[window.google.picker.Response.DOCUMENTS] && data[window.google.picker.Response.DOCUMENTS][0] &&
1390
			data[window.google.picker.Response.DOCUMENTS][0].id)
1391
		{
1392
			const request = new window.XMLHttpRequest();
1393
			request.open('GET', 'https://www.googleapis.com/drive/v2/files/' + data[window.google.picker.Response.DOCUMENTS][0].id);
1394
			request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
1395
			request.addEventListener('load', () => {
1396
				if (request && request.responseText)
1397
				{
1398
					const
1399
						response = window.JSON.parse(request.responseText),
1400
						fExport = (item, mimeType, ext) => {
1401
							if (item && item.exportLinks)
1402
							{
1403
								if (item.exportLinks[mimeType])
1404
								{
1405
									response.downloadUrl = item.exportLinks[mimeType];
1406
									response.title = item.title + '.' + ext;
1407
									response.mimeType = mimeType;
1408
								}
1409
								else if (item.exportLinks['application/pdf'])
1410
								{
1411
									response.downloadUrl = item.exportLinks['application/pdf'];
1412
									response.title = item.title + '.pdf';
1413
									response.mimeType = 'application/pdf';
1414
								}
1415
							}
1416
						};
1417
1418
					if (response && !response.downloadUrl && response.mimeType && response.exportLinks)
1419
					{
1420
						switch (response.mimeType.toString().toLowerCase())
1421
						{
1422
							case 'application/vnd.google-apps.document':
1423
								fExport(response, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx');
1424
								break;
1425
							case 'application/vnd.google-apps.spreadsheet':
1426
								fExport(response, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx');
1427
								break;
1428
							case 'application/vnd.google-apps.drawing':
1429
								fExport(response, 'image/png', 'png');
1430
								break;
1431
							case 'application/vnd.google-apps.presentation':
1432
								fExport(response, 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pptx');
1433
								break;
1434
							default:
1435
								fExport(response, 'application/pdf', 'pdf');
1436
								break;
1437
						}
1438
					}
1439
1440
					if (response && response.downloadUrl)
1441
					{
1442
						this.addDriveAttachment(response, accessToken);
1443
					}
1444
				}
1445
			});
1446
1447
			request.send();
1448
		}
1449
	}
1450
1451
	driveCreatePiker(authToken) {
1452
		if (window.gapi && authToken && authToken.access_token)
1453
		{
1454
			window.gapi.load('picker', {
1455
				callback: () => {
1456
					if (window.google && window.google.picker)
1457
					{
1458
						const drivePicker = new window.google.picker.PickerBuilder()
1459
							// .addView(window.google.picker.ViewId.FOLDERS)
1460
							.addView(window.google.picker.ViewId.DOCS)
1461
							.setAppId(Settings.settingsGet('GoogleClientID'))
1462
							.setOAuthToken(authToken.access_token)
1463
							.setCallback(_.bind(this.driveCallback, this, authToken.access_token))
1464
							.enableFeature(window.google.picker.Feature.NAV_HIDDEN)
1465
							// .setOrigin(window.location.protocol + '//' + window.location.host)
1466
							.build();
1467
1468
						drivePicker.setVisible(true);
1469
					}
1470
				}
1471
			});
1472
		}
1473
	}
1474
1475
	driveOpenPopup() {
1476
		if (window.gapi)
1477
		{
1478
			window.gapi.load('auth', {
1479
				callback: () => {
1480
					const
1481
						authToken = window.gapi.auth.getToken(),
1482
						fResult = (authResult) => {
1483
							if (authResult && !authResult.error)
1484
							{
1485
								const token = window.gapi.auth.getToken();
1486
								if (token)
1487
								{
1488
									this.driveCreatePiker(token);
1489
								}
1490
1491
								return true;
1492
							}
1493
1494
							return false;
1495
						};
1496
1497
					if (!authToken)
1498
					{
1499
						window.gapi.auth.authorize({
1500
							'client_id': Settings.settingsGet('GoogleClientID'),
1501
							'scope': 'https://www.googleapis.com/auth/drive.readonly',
1502
							'immediate': true
1503
						}, (authResult) => {
1504
							if (!fResult(authResult))
1505
							{
1506
								window.gapi.auth.authorize({
1507
									'client_id': Settings.settingsGet('GoogleClientID'),
1508
									'scope': 'https://www.googleapis.com/auth/drive.readonly',
1509
									'immediate': false
1510
								}, fResult);
1511
							}
1512
						});
1513
					}
1514
					else
1515
					{
1516
						this.driveCreatePiker(authToken);
1517
					}
1518
				}
1519
			});
1520
		}
1521
	}
1522
1523
	/**
1524
	 * @param {string} id
1525
	 * @returns {?Object}
1526
	 */
1527
	getAttachmentById(id) {
1528
		return _.find(this.attachments(), (item) => item && id === item.id);
1529
	}
1530
1531
	cancelAttachmentHelper(id, oJua) {
1532
		return () => {
1533
			const attachment = _.find(this.attachments(), (item) => item && item.id === id);
1534
			if (attachment)
1535
			{
1536
				this.attachments.remove(attachment);
1537
				delegateRunOnDestroy(attachment);
1538
1539
				if (oJua)
1540
				{
1541
					oJua.cancel(id);
1542
				}
1543
			}
1544
		};
1545
	}
1546
1547
	initUploader() {
1548
		if (this.composeUploaderButton())
1549
		{
1550
			const
1551
				uploadCache = {},
1552
				attachmentSizeLimit = pInt(Settings.settingsGet('AttachmentLimit')),
1553
				oJua = new Jua({
1554
					'action': upload(),
1555
					'name': 'uploader',
1556
					'queueSize': 2,
1557
					'multipleSizeLimit': 50,
1558
					'clickElement': this.composeUploaderButton(),
1559
					'dragAndDropElement': this.composeUploaderDropPlace()
1560
				});
1561
1562
			if (oJua)
1563
			{
1564
				oJua
1565
					// .on('onLimitReached', (limit) => {
1566
					// 	alert(limit);
1567
					// })
1568
					.on('onDragEnter', () => {
1569
						this.dragAndDropOver(true);
1570
					})
1571
					.on('onDragLeave', () => {
1572
						this.dragAndDropOver(false);
1573
					})
1574
					.on('onBodyDragEnter', () => {
1575
						this.attachmentsPlace(true);
1576
						this.dragAndDropVisible(true);
1577
					})
1578
					.on('onBodyDragLeave', () => {
1579
						this.dragAndDropVisible(false);
1580
					})
1581
					.on('onProgress', (id, loaded, total) => {
1582
1583
						let item = uploadCache[id];
1584
						if (!item)
1585
						{
1586
							item = this.getAttachmentById(id);
1587
							if (item)
1588
							{
1589
								uploadCache[id] = item;
1590
							}
1591
						}
1592
1593
						if (item)
1594
						{
1595
							item.progress(window.Math.floor(loaded / total * 100));
1596
						}
1597
1598
					})
1599
					.on('onSelect', (sId, oData) => {
1600
1601
						this.dragAndDropOver(false);
1602
1603
						const
1604
							fileName = isUnd(oData.FileName) ? '' : oData.FileName.toString(),
1605
							size = isNormal(oData.Size) ? pInt(oData.Size) : null,
1606
							attachment = new ComposeAttachmentModel(sId, fileName, size);
1607
1608
						attachment.cancel = this.cancelAttachmentHelper(sId, oJua);
1609
1610
						this.attachments.push(attachment);
1611
1612
						this.attachmentsPlace(true);
1613
1614
						if (0 < size && 0 < attachmentSizeLimit && attachmentSizeLimit < size)
1615
						{
1616
							attachment
1617
								.waiting(false).uploading(true).complete(true)
1618
								.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
1619
1620
							return false;
1621
						}
1622
1623
						return true;
1624
					})
1625
					.on('onStart', (id) => {
1626
1627
						let item = uploadCache[id];
1628
						if (!item)
1629
						{
1630
							item = this.getAttachmentById(id);
1631
							if (item)
1632
							{
1633
								uploadCache[id] = item;
1634
							}
1635
						}
1636
1637
						if (item)
1638
						{
1639
							item.waiting(false).uploading(true).complete(false);
1640
						}
1641
					})
1642
					.on('onComplete', (id, result, data) => {
1643
1644
						const
1645
							attachment = this.getAttachmentById(id),
1646
							errorCode = data && data.Result && data.Result.ErrorCode ? data.Result.ErrorCode : null,
1647
							attachmentJson = result && data && data.Result && data.Result.Attachment ? data.Result.Attachment : null;
1648
1649
						let error = '';
1650
						if (null !== errorCode)
1651
						{
1652
							error = getUploadErrorDescByCode(errorCode);
1653
						}
1654
						else if (!attachmentJson)
1655
						{
1656
							error = i18n('UPLOAD/ERROR_UNKNOWN');
1657
						}
1658
1659
						if (attachment)
1660
						{
1661
							if ('' !== error && 0 < error.length)
1662
							{
1663
								attachment
1664
									.waiting(false)
1665
									.uploading(false)
1666
									.complete(true)
1667
									.error(error);
1668
							}
1669
							else if (attachmentJson)
1670
							{
1671
								attachment
1672
									.waiting(false)
1673
									.uploading(false)
1674
									.complete(true);
1675
1676
								attachment.initByUploadJson(attachmentJson);
1677
							}
1678
1679
							if (isUnd(uploadCache[id]))
1680
							{
1681
								delete (uploadCache[id]);
1682
							}
1683
						}
1684
					});
1685
1686
				this
1687
					.addAttachmentEnabled(true)
1688
					.dragAndDropEnabled(oJua.isDragAndDropSupported());
1689
			}
1690
			else
1691
			{
1692
				this
1693
					.addAttachmentEnabled(false)
1694
					.dragAndDropEnabled(false);
1695
			}
1696
		}
1697
	}
1698
1699
	/**
1700
	 * @returns {Object}
1701
	 */
1702
	prepearAttachmentsForSendOrSave() {
1703
		const result = {};
1704
		_.each(this.attachmentsInReady(), (item) => {
1705
			if (item && '' !== item.tempName() && item.enabled())
1706
			{
1707
				result[item.tempName()] = [
1708
					item.fileName(),
1709
					item.isInline ? '1' : '0',
1710
					item.CID,
1711
					item.contentLocation
1712
				];
1713
			}
1714
		});
1715
1716
		return result;
1717
	}
1718
1719
	/**
1720
	 * @param {MessageModel} message
1721
	 */
1722
	addMessageAsAttachment(message) {
1723
		if (message)
1724
		{
1725
			let temp = message.subject();
1726
			temp = '.eml' === temp.substr(-4).toLowerCase() ? temp : temp + '.eml';
1727
1728
			const attachment = new ComposeAttachmentModel(
1729
				message.requestHash, temp, message.size()
1730
			);
1731
1732
			attachment.fromMessage = true;
1733
			attachment.cancel = this.cancelAttachmentHelper(message.requestHash);
1734
			attachment.waiting(false).uploading(true).complete(true);
1735
1736
			this.attachments.push(attachment);
1737
		}
1738
	}
1739
1740
	/**
1741
	 * @param {string} url
1742
	 * @param {string} name
1743
	 * @param {number} size
1744
	 * @returns {ComposeAttachmentModel}
1745
	 */
1746
	addAttachmentHelper(url, name, size) {
1747
		const attachment = new ComposeAttachmentModel(url, name, size);
1748
1749
		attachment.fromMessage = false;
1750
		attachment.cancel = this.cancelAttachmentHelper(url);
1751
		attachment.waiting(false).uploading(true).complete(false);
1752
1753
		this.attachments.push(attachment);
1754
1755
		this.attachmentsPlace(true);
1756
1757
		return attachment;
1758
	}
1759
1760
	/**
1761
	 * @param {Object} dropboxFile
1762
	 * @returns {boolean}
1763
	 */
1764
	addDropboxAttachment(dropboxFile) {
1765
		const
1766
			attachmentSizeLimit = pInt(Settings.settingsGet('AttachmentLimit')),
1767
			mSize = dropboxFile.bytes,
1768
			attachment = this.addAttachmentHelper(dropboxFile.link, dropboxFile.name, mSize);
1769
1770
		if (0 < mSize && 0 < attachmentSizeLimit && attachmentSizeLimit < mSize)
1771
		{
1772
			attachment.uploading(false).complete(true);
1773
			attachment.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
1774
			return false;
1775
		}
1776
1777
		Remote.composeUploadExternals((statusResult, data) => {
1778
1779
			let result = false;
1780
			attachment.uploading(false).complete(true);
1781
1782
			if (StorageResultType.Success === statusResult && data && data.Result)
1783
			{
1784
				if (data.Result[attachment.id])
1785
				{
1786
					result = true;
1787
					attachment.tempName(data.Result[attachment.id]);
1788
				}
1789
			}
1790
1791
			if (!result)
1792
			{
1793
				attachment.error(getUploadErrorDescByCode(UploadErrorCode.FileNoUploaded));
1794
			}
1795
1796
		}, [dropboxFile.link]);
1797
1798
		return true;
1799
	}
1800
1801
	/**
1802
	 * @param {Object} driveFile
1803
	 * @param {string} accessToken
1804
	 * @returns {boolean}
1805
	 */
1806
	addDriveAttachment(driveFile, accessToken) {
1807
		const
1808
			attachmentSizeLimit = pInt(Settings.settingsGet('AttachmentLimit')),
1809
			size = driveFile.fileSize ? pInt(driveFile.fileSize) : 0,
1810
			attachment = this.addAttachmentHelper(driveFile.downloadUrl, driveFile.title, size);
1811
1812
		if (0 < size && 0 < attachmentSizeLimit && attachmentSizeLimit < size)
1813
		{
1814
			attachment.uploading(false).complete(true);
1815
			attachment.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
1816
			return false;
1817
		}
1818
1819
		Remote.composeUploadDrive((statusResult, data) => {
1820
1821
			let result = false;
1822
			attachment.uploading(false).complete(true);
1823
1824
			if (StorageResultType.Success === statusResult && data && data.Result)
1825
			{
1826
				if (data.Result[attachment.id])
1827
				{
1828
					result = true;
1829
					attachment.tempName(data.Result[attachment.id][0]);
1830
					attachment.size(pInt(data.Result[attachment.id][1]));
1831
				}
1832
			}
1833
1834
			if (!result)
1835
			{
1836
				attachment.error(getUploadErrorDescByCode(UploadErrorCode.FileNoUploaded));
1837
			}
1838
1839
		}, driveFile.downloadUrl, accessToken);
1840
1841
		return true;
1842
	}
1843
1844
	/**
1845
	 * @param {MessageModel} message
1846
	 * @param {string} type
1847
	 */
1848
	prepearMessageAttachments(message, type) {
1849
		if (message)
1850
		{
1851
			if (ComposeType.ForwardAsAttachment === type)
1852
			{
1853
				this.addMessageAsAttachment(message);
1854
			}
1855
			else
1856
			{
1857
				const attachments = message.attachments();
1858
				_.each(isNonEmptyArray(attachments) ? attachments : [], (item) => {
1859
					let add = false;
1860
					switch (type)
1861
					{
1862
						case ComposeType.Reply:
1863
						case ComposeType.ReplyAll:
1864
							add = item.isLinked;
1865
							break;
1866
1867
						case ComposeType.Forward:
1868
						case ComposeType.Draft:
1869
						case ComposeType.EditAsNew:
1870
							add = true;
1871
							break;
1872
						// no default
1873
					}
1874
1875
					if (add)
1876
					{
1877
						const attachment = new ComposeAttachmentModel(
1878
							item.download, item.fileName, item.estimatedSize,
1879
							item.isInline, item.isLinked, item.cid, item.contentLocation
1880
						);
1881
1882
						attachment.fromMessage = true;
1883
						attachment.cancel = this.cancelAttachmentHelper(item.download);
1884
						attachment.waiting(false).uploading(true).complete(false);
1885
1886
						this.attachments.push(attachment);
1887
					}
1888
				});
1889
			}
1890
		}
1891
	}
1892
1893
	removeLinkedAttachments() {
1894
		const arrachment = _.find(this.attachments(), (item) => item && item.isLinked);
1895
		if (arrachment)
1896
		{
1897
			this.attachments.remove(arrachment);
1898
			delegateRunOnDestroy(arrachment);
1899
		}
1900
	}
1901
1902
	setMessageAttachmentFailedDownloadText() {
1903
		_.each(this.attachments(), (attachment) => {
1904
			if (attachment && attachment.fromMessage)
1905
			{
1906
				attachment
1907
					.waiting(false)
1908
					.uploading(false)
1909
					.complete(true)
1910
					.error(getUploadErrorDescByCode(UploadErrorCode.FileNoUploaded));
1911
			}
1912
		});
1913
	}
1914
1915
	/**
1916
	 * @param {boolean=} includeAttachmentInProgress = true
1917
	 * @returns {boolean}
1918
	 */
1919
	isEmptyForm(includeAttachmentInProgress = true) {
1920
1921
		const withoutAttachment = includeAttachmentInProgress ?
1922
			0 === this.attachments().length : 0 === this.attachmentsInReady().length;
1923
1924
		return 0 === this.to().length &&
1925
			0 === this.cc().length &&
1926
			0 === this.bcc().length &&
1927
			0 === this.replyTo().length &&
1928
			0 === this.subject().length &&
1929
			withoutAttachment &&
1930
			(!this.oEditor || '' === this.oEditor.getData());
1931
	}
1932
1933
	reset() {
1934
		this.to('');
1935
		this.cc('');
1936
		this.bcc('');
1937
		this.replyTo('');
1938
		this.subject('');
1939
1940
		this.requestDsn(false);
1941
		this.requestReadReceipt(false);
1942
		this.markAsImportant(false);
1943
1944
		this.attachmentsPlace(false);
1945
1946
		this.aDraftInfo = null;
1947
		this.sInReplyTo = '';
1948
		this.bFromDraft = false;
1949
		this.sReferences = '';
1950
1951
		this.sendError(false);
1952
		this.sendSuccessButSaveError(false);
1953
		this.savedError(false);
1954
		this.savedTime(0);
1955
		this.emptyToError(false);
1956
		this.attachmentsInProcessError(false);
1957
1958
		this.showCc(false);
1959
		this.showBcc(false);
1960
		this.showReplyTo(false);
1961
1962
		delegateRunOnDestroy(this.attachments());
1963
		this.attachments([]);
1964
1965
		this.dragAndDropOver(false);
1966
		this.dragAndDropVisible(false);
1967
1968
		this.draftFolder('');
1969
		this.draftUid('');
1970
1971
		this.sending(false);
1972
		this.saving(false);
1973
1974
		if (this.oEditor)
1975
		{
1976
			this.oEditor.clear(false);
1977
		}
1978
	}
1979
1980
	/**
1981
	 * @returns {Array}
1982
	 */
1983
	getAttachmentsDownloadsForUpload() {
1984
		return _.map(_.filter(
1985
			this.attachments(), (item) => item && '' === item.tempName(),
1986
		), (item) => item.id);
1987
	}
1988
1989
	resizerTrigger() {
1990
		this.resizer(!this.resizer());
1991
	}
1992
}
1993
1994
module.exports = ComposePopupView;
1995